
assert(TheWorld and TheWorld.Map, "使用worldutil库必须保证TheWorld存在！")
local w_util = {}
local t_util = require "libs/tableutil"
local e_util = require "libs/entutil"
local c_util = require "libs/calcutil"

local Last_time = 88888888
-- 获取世界时间
function w_util:GetWorldTime()
    local state_time = TheWorld.state.time
    local state_cycles = TheWorld.state.cycles
    local day_time = TUNING.TOTAL_DAY_TIME
    local sys_time = (state_cycles + state_time) * day_time
    local diff_time = sys_time - Last_time -- 正常来说 diff应该在0-2之间
    if state_time < 0.98 or diff_time > day_time + 5 or diff_time < day_time - 5 then
        -- 时间锁, 客户端也会检查服务器是否发送错误的时间
        Last_time = sys_time
    end
    return Last_time
end

-- 获取此游戏角色的本次游玩时间（更换游戏角色或者上下洞穴会重新记录）
function w_util:GetTimePlaying()
    local now = GetTimePlaying()
    return now - now % 1
end

-- 将时间转换为 天数-分-秒 的格式
function w_util:FormatTime(time)
    if time > TUNING.TOTAL_DAY_TIME then
        local day = math.floor(time / TUNING.TOTAL_DAY_TIME)
        local modtime = math.floor(time) % TUNING.TOTAL_DAY_TIME
        local minute = math.floor(modtime / 60)
        local second = math.floor(modtime) % 60
        return string.format("%02d-%02d:%02d", day, minute, second)
    else
        local minute = math.floor(time / 60)
        local second = math.floor(time) % 60
        return string.format("%02d:%02d", minute, second)
    end
end

-- 监听世界某个事件，并执行函数
function w_util:SetBindEvent(eventname, func)
    TheWorld:RemoveEventCallback(eventname, func)
    TheWorld:ListenForEvent(eventname, func)
end

-- 监听事件世界状态，并执行函数
function w_util:SetBindState(state, func)
    TheWorld:StopWatchingWorldState(state, func)
    TheWorld:WatchWorldState(state, func)
end

-- 是否为没有光的夜晚
function w_util:IsDarkNight()
    local wd = TheWorld.state
    if wd then
        return wd.isnight and not wd.isfullmoon
    end
end

-- 获取世界种子
function w_util:GetSeed()
    return TheWorld.meta and TheWorld.meta.session_identifier
end

local map__tile, map__pos
function w_util:GetMapTiles(getnew)
    if not map__tile or not map__pos or getnew then
        local map = TheWorld.Map
        local w = map:GetWorldSize()
        local l_init = (-w / 2) * 4
        local l_end = -l_init
        map__tile = {}
        map__pos = {}
        for pos_x = l_init, l_end, 4 do
            map__pos[pos_x] = {}
            for pos_y = l_init, l_end, 4 do
                local tile = map:GetTileAtPoint(pos_x, 0, pos_y)
                map__pos[pos_x][pos_y] = tile
                if not map__tile[tile] then
                    map__tile[tile] = {}
                end
                table.insert(map__tile[tile], {x=pos_x, y=pos_y})
            end
        end
    end
    return map__tile, map__pos
end


function w_util:GetLayoutData(layout_src)
    local GetLayout = require("map/static_layout").Get
    local require_status, layout_file = pcall(require, layout_src)
    if not require_status then
        return
    end
    local LayoutData = GetLayout(layout_src)
    return type(LayoutData.ground) == "table" and LayoutData
end

function w_util:GetLayoutResult(layout_src, tiles_deny, layer)
    local LayoutData = self:GetLayoutData(layout_src)
    if not LayoutData then
        return
    end
    layer = layer or 0
    local ground_data = LayoutData.ground
    local count_lines = #ground_data+layer*2
    local count_cols = #(ground_data[1])+layer*2
    if count_lines < 2 or count_cols < 2 then
        return
    end

    local layout_data = {}
    for i = 1,count_lines do
        layout_data[i] = {}
        for j = 1,count_cols do
            local x,y = i-layer, j-layer
            layout_data[i][j] = ground_data[x] and ground_data[x][y] or 0
        end
    end

    -- 调试：用来打印地图
    -- if layout_src == "map/static_layouts/dragonfly_arena" then
    --     t_util:Pairs(layout_data, function(line, linedata)
    --         local str = ""
    --         t_util:Pairs(linedata, function(col, tiledata)
    --             local tile = LayoutData.ground_types[tiledata]
    --             if tile then
    --                 str = str .. tile .. ","
    --             else
    --                 str = str .. "0" .. ","
    --             end
    --         end)
    --         str = str .. "\n"
    --         print(str)
    --     end)
    -- end

    local map_tile, map_pos = self:GetMapTiles()
    -- 不应该从有效地皮开始检索，而是从世界地皮中最少的那种开始检索
    local key_tile
    t_util:Pairs(ground_data, function(line, linedata)
        t_util:Pairs(linedata, function(col, tiledata)
            local tile = LayoutData.ground_types[tiledata]
            if tile then
                local mt = map_tile[tile]
                if mt and (not key_tile or key_tile.count > #mt) then
                    key_tile = {
                        x = line+layer,
                        y = col+layer,
                        tile = tile,
                        count = #mt
                    }
                end
            end
        end)
    end)
    if not key_tile then
        return
    end

    local result = {
        layout = LayoutData.layout, -- 返回的布局数据
        pos_list = {}, -- 一个表，其中x,y是中心坐标, m是布局方向
        height = count_lines * 4, -- 宽高
        width = count_cols * 4
    }
    -- 返回满足所有布局的中心位置
    t_util:IPairs(map_tile[key_tile.tile] or {}, function(pos)
        local pos_x, pos_y = pos.x, pos.y
        local k_x, k_y = 4 * (key_tile.x - 1), 4 * (key_tile.y - 1)
        -- 遍历布局，挨个比对
        local ori = {1, 2, 3, 4, 5, 6, 7, 8}
        if not t_util:GetElement(layout_data, function(line, linedata)
            return t_util:GetElement(linedata, function(col, tiledata)
                if not tiles_deny and tiledata == 0 then
                    return
                end
                local layout_tile = LayoutData.ground_types[tiledata]
                local lt_x, lt_y = (line - 1) * 4, (col - 1) * 4
                -- 这里可以进行四个方向的计算！
                t_util:Pairs(ori, function(mode, value)
                    if not value then
                        return
                    end
                    local vx, vy = c_util:ModeToXY(mode, lt_x - k_x, lt_y - k_y, pos_x, pos_y)
                    local world_tile = map_pos[vx] and map_pos[vx][vy]
                    -- 这里能化简，可惜我CPU转不动了
                    if not world_tile
                    or (tiles_deny and ((not layout_tile and table.contains(tiles_deny, world_tile) or (layout_tile and world_tile ~= layout_tile))))
                    or (not tiles_deny and world_tile ~= layout_tile)
                    then
                        ori[mode] = false
                    end
                end)
                -- 如果方向not可持续，返回true，结束当前循环
                if not t_util:GetElement(ori, function(mode, value)
                    return value
                end) then
                    return true
                end
            end)
        end) then
            local mode = t_util:GetElement(ori, function(mode, value)
                return value and mode
            end)
            local cx, cy = c_util:ModeToXY(mode, 2 * count_cols - k_x - 2, 2 * count_lines - k_y - 2, pos_x, pos_y)
            table.insert(result.pos_list, {
                x = cx, -- 中心点坐标
                y = cy,
                m = mode -- 排列方向
            })
        end
    end)
    return result
end

function w_util:GetTilesResult(tiles, tiles_deny)
    local map_tile, map_pos = self:GetMapTiles()
    local count_lines = #tiles
    local count_cols = #(tiles[1])

    -- 不应该从有效地皮开始检索，而是从世界地皮中最少的那种开始检索
    local key_tile
    t_util:Pairs(tiles, function(line, linedata)
        t_util:Pairs(linedata, function(col, tile)
            local mt = map_tile[tile]
            if mt and (not key_tile or key_tile.count > #mt) then
                key_tile = {
                    x = line,
                    y = col,
                    tile = tile,
                    count = #mt
                }
            end
        end)
    end)
    if not key_tile then
        return
    end

    local pos_list = {}
    t_util:IPairs(map_tile[key_tile.tile] or {}, function(pos)
        local pos_x, pos_y = pos.x, pos.y
        local k_x, k_y = 4 * (key_tile.x - 1), 4 * (key_tile.y - 1)
        -- 遍历布局，挨个比对
        local ori = {1, 2, 3, 4, 5, 6, 7, 8}
        if not t_util:GetElement(tiles, function(line, linedata)
            return t_util:GetElement(linedata, function(col, layout_tile)
                if not tiles_deny and layout_tile == 0 then
                    return
                end
                local lt_x, lt_y = (line - 1) * 4, (col - 1) * 4
                -- 这里可以进行四个方向的计算！
                t_util:Pairs(ori, function(mode, value)
                    if not value then
                        return
                    end
                    local vx, vy = c_util:ModeToXY(mode, lt_x - k_x, lt_y - k_y, pos_x, pos_y)
                    local world_tile = map_pos[vx] and map_pos[vx][vy]
                    -- 这里能化简，可惜我CPU转不动了
                    if not world_tile
                    or (tiles_deny and ((layout_tile==0 and table.contains(tiles_deny, world_tile) or (layout_tile~=0 and world_tile~=layout_tile))))
                    or (not tiles_deny and world_tile ~= layout_tile)
                    then
                        ori[mode] = false
                    end
                end)
                -- 如果方向not可持续，返回true，结束当前循环
                if not t_util:GetElement(ori, function(mode, value)
                    return value
                end) then
                    return true
                end
            end)
        end) then
            local mode = t_util:GetElement(ori, function(mode, value)
                return value and mode
            end)
            local cx, cy = c_util:ModeToXY(mode, 2 * count_cols - k_x - 2, 2 * count_lines - k_y - 2, pos_x, pos_y)
            table.insert(pos_list, {
                x = cx, -- 中心点坐标
                y = cy,
                m = mode -- 排列方向
            })
        end
    end)

    return pos_list
end

return w_util
